#include "SimulationManager.h"
#include <cstdlib>

#include <set>

#define STEREO 0


static int SM_MAX_Y = 512;

//Update physics of the simulation
//Input is timestep.
void SimulationManager::_DefaultSMUpdate( float ts ){}

bool SimulationManager::_DefaultSMInitWindow(int w, int h) {
	//Must initialize GLFW first
	if (!glfwInit() ) {
		return false;
	}

#if STEREO == 1
	glfwOpenWindowHint( GLFW_STEREO, GL_TRUE );
#endif

	if (!glfwOpenWindow(w, h, 8, 8, 8, 8, 24, 8,GLFW_WINDOW)) {
		glfwTerminate();
		return false;
	}


	return true;
}

void SimulationManager::_DefaultSMInitFrames() {	
	glMatrixMode(GL_PROJECTION);
	glLoadMatrixf(&projMat[0][0]);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glLoadMatrixf(&mvMat[0][0]);

	glm::mat4 rotmat;

	glTranslatef(mDataCenter.x, mDataCenter.y, mDataCenter.z);
	glMultMatrixf(&rotmat[0][0]);

	//Translate first
	glTranslatef(-mDataCenter.x, -mDataCenter.y, -mDataCenter.z);

	glClearColor(1.0, 1.0, 1.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void SimulationManager::_DefaultSMLoadResources() {
}

//Display code
void SimulationManager::_DefaultSMDisplay() {
	glClearColor(1.0, 1.0, 1.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);
}

void SimulationManager::_DefaultSMEndFrames() {
	glfwSwapBuffers();
}

//Class constructor
SimulationManager::SimulationManager(int w, int h) 
	: mTS (0.03f),
	  mWindowW(w),
	  mWindowH(h),
	  InitWindow(NULL),
	  InitFrames(NULL),
	  Update(NULL),
	  Display(NULL),
	  mIsRunning(false),
	  mIsPaused(false)
{

	SM_MAX_Y = h;
	//Default projection and modelview matrices
	SetProjectionMatrix(&PerspectiveProjectionMatrix(75.0f, float(w)/float(h), 0.01f, 1000.0f)[0][0]);

	SetModelviewMatrix( &mvMat[0][0] );

	SetCallbackInitWindow(NULL);
	SetCallbackInitFrames(NULL);
	SetCallbackUpdate(NULL);
	SetCallbackDisplay(NULL);
	SetCallbackEndFrames(NULL);
}

//Class deconstructor
SimulationManager::~SimulationManager() {
}

//Run the simulation
int SimulationManager::Run(){
	InitWindow == NULL ? 
		this->_DefaultSMInitWindow(mWindowW, mWindowH) :
		InitWindow() ;

	InitInputCallbacks();


	LoadResources == NULL ?
		_DefaultSMLoadResources() :
		LoadResources();

	MainLoop();

	return EXIT_SUCCESS;
}

//The main loop for the simulation.
void SimulationManager::MainLoop() {

	double currentTime = glfwGetTime();
	double acc = 0.0;
	double newtime;
	double frameTime;
	mIsRunning = true;

	while (mIsRunning) {
		//Poll input and calls any callback functions.
		PollUserInput();
		//printf("timeSinceLastupdate: %f\n", (float)timeSinceLastUpdate);
		if (!mIsPaused) {
			newtime = glfwGetTime();
			frameTime = newtime - currentTime;
			currentTime = newtime;

			acc += frameTime;
			//Update state of simulation
			//if (IsTimeToUpdate(timeSinceLastUpdate)) {
			unsigned int numtimes = 0;
			while (acc >= mTS && (numtimes++ < 3) ) {
				Update == NULL ? 
					_DefaultSMUpdate(mTS) :
					Update(mTS);
				acc -= mTS;
			}
			//}
		}
		else {
			currentTime = glfwGetTime();
			acc = 0.0;
		}
		//Perform the user-defined InitFrames callback.
		InitFrames == NULL ?
			_DefaultSMInitFrames() :
			InitFrames();

		//Perform the user-defined Display callback.
		Display == NULL ?
			_DefaultSMDisplay() :
			Display();

		EndFrames == NULL ?
			_DefaultSMEndFrames() :
			EndFrames();
	}
}

//Input is the time since the last update step.
bool SimulationManager::IsTimeToUpdate( double t) {
	if (t > mTS) {
		return true;
	}

	return false;
}


//Can Do something different here.
void SimulationManager::InitInputCallbacks() {

	//glfwSetCharCallback(KeyboardCallback);
}

void SimulationManager::Quit() {
	mIsRunning = false;
}

void SimulationManager::PollUserInput() {

	static std::set<int> pressedKeys;

	for (std::map<int, SM_FUNC_PTR>::iterator it = mKeyMap.begin(); 
		it != mKeyMap.end();
		++it) {
			int currKey = it->first;

			//If the key is being pressed
			if (glfwGetKey(currKey) == GLFW_PRESS) {
				//If it is newly pressed, call function.
				if (pressedKeys.insert(currKey).second){
					(it->second)();
				}

			}
			//Not being pressed.
			else {
				//If it is still in the pressed key list, remove it.
				std::set<int>::iterator it = pressedKeys.find(currKey);
				if ( it != pressedKeys.end() ){
					pressedKeys.erase(it);
				}
			}
	}
}

void SimulationManager::SetModelviewMatrix( const float * mvmat ) {
	std::copy(mvmat, mvmat+16, &mvMat[0][0]);
}

void SimulationManager::SetProjectionMatrix( const float * projMat ) {
	std::copy(projMat, projMat+16, &this->projMat[0][0]);
}

glm::mat4x4 SimulationManager::PerspectiveProjectionMatrix( float fovDeg, float aspect, float znear, float zfar ) {
	return glm::perspective(fovDeg, aspect, znear, zfar);
}

glm::mat4x4 SimulationManager::OrthogonalProjectionMatrix( float left, float right, float bottom, float top, float znear, float zfar ){
	 return glm::ortho(left, right, bottom, top, znear, zfar);
}

//Only works in 2D mode.
//void SimulationManager::Set3DPerspectiveProjection( float fovDeg, float aspect, float znear, float zfar ) {
//	projMat = glm::perspective(fovDeg, aspect, znear, zfar);
//}
//
//void SimulationManager::Set2DOrthogonalProjection( float left, float right, float bottom, float top, float znear, float zfar ){
//	projMat = glm::ortho(left, right, bottom, top, znear, zfar);
//}

void SimulationManager::SetLookAt( const float * eyepos, const float * lookatpos, const float * up ) {
	mvMat = glm::lookAt( glm::vec3(eyepos[0], eyepos[1], eyepos[2]),
		glm::vec3(lookatpos[0], lookatpos[1], lookatpos[2]),
		glm::vec3(up[0], up[1], up[2]) );

	mDataCenter = glm::vec3(lookatpos[0], lookatpos[1], lookatpos[2]);
}

